/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package recommender.bayesian;

/**
 * A class to build a knowledge base network
 * @author Aya
 */

import java.awt.Color;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import recommender.objects.Entity;
import recommender.objects.Parent;
import recommender.objects.Video;
import smile.Network;

public class BayesianBuilder {
    
    Network kbnet;
    static JSONObject net;
    static JSONArray nodes;
    static JSONArray links;
    static Map<String, Integer> ids;
    
    static String path;
    static final double uncertainty = 0.01;
    static final double annotation_uncertainty = 0.1;
    static final double relation_uncertainty = 0.1;
    
    private boolean[] getPattern(int num, int length){
        boolean[] pattern = new boolean[length];
        String binary = Integer.toBinaryString(num);
        for(int i=pattern.length-1, j=binary.length()-1; 
                i>=0; i--, j--){
            if(j >= 0)
                pattern[i] = binary.charAt(j)=='1'? (false) : (true);
            else
                pattern[i] = true;
        }
        return pattern;
    }
    
    private double getProbability(double[] probs, boolean[] pattern){
        double prob = (1-uncertainty);
        for(int i=0; i<probs.length; i++){
            prob *= pattern[i]? 1-probs[i]: 1;
        }
        double p = pattern[pattern.length-1]? (1-prob):prob;
        return p;
    }
    
    /**
     * Computes the Conditional Probability Table entries of a node
     * @param parents The probabilities connecting parents to the node
     * @return CPT entries in the format which the SMILE library consumes
     */
    private double[] getCPT(double[] parents){
        double[] cpt;
        if(parents.length==0){
            cpt = new double[]{0.5, 0.5};
        }
        else
        {
            // Introduce a penalty for entities with more parents
            double idf_penalty = 0.5 + (Math.exp(-1*parents.length));
            for(int i=0; i<parents.length; i++)
                parents[i] *= idf_penalty;
            int cpt_entries = (int)Math.pow(2, parents.length+1);
            cpt = new double[cpt_entries];
            for(int i=0; i<cpt.length; i++){
                boolean[] pattern = getPattern(i, parents.length+1);
                cpt[i] = getProbability(parents, pattern);
            }
        }
        return cpt;
    }
    
     private boolean contains(String[] arr, String s){
        for(String a: arr){
            if(a.equals(s))
                return true;
        }
        return false;
    }
    
    /****************** PUBLIC FUNCTIONS *******************/ 
    
    /**
     * 
     * @param p
     * @param name 
     */
    public BayesianBuilder(String p, String name){        
        path = p;
        kbnet = new Network();
        kbnet.setName(name);
        net = new JSONObject();
        nodes = new JSONArray();
        links = new JSONArray();
        ids = new HashMap<>();
    }
    
    /**
     * 
     * @param p
     * @param name
     * @param file 
     */
    public BayesianBuilder(String p, String name, String file){        
        path = p;
        kbnet = new Network();
        kbnet.readFile(path+"/"+file);
        kbnet.setName(name);
    } 
     
    /**
     * 
     * @param entity 
     */ 
    public void createNode(Entity entity){
        kbnet.addNode(Network.NodeType.Cpt, entity.getIdentifier());
        kbnet.setNodeName(entity.getIdentifier(), entity.getName());
        kbnet.setOutcomeId(entity.getIdentifier(), 0, "Like");
        kbnet.setOutcomeId(entity.getIdentifier(), 1, "Dislike");
        JSONObject node = new JSONObject();
        node.put("name", entity.getName());
        ids.put(entity.getIdentifier(), nodes.size());
        if(entity.isClass()){
            node.put("group", 1);
            kbnet.setNodeBgColor(entity.getIdentifier(), Color.GRAY);
        } else {
            node.put("group", 2);
            kbnet.setNodeBgColor(entity.getIdentifier(), Color.WHITE);
        }
        nodes.add(node);
    }
    
    /**
     * 
     * @param video 
     */
    public void createNode(Video video){
        kbnet.addNode(Network.NodeType.Cpt, video.getIdentifier());
        kbnet.setNodeName(video.getIdentifier(), video.getTitle());
        kbnet.setOutcomeId(video.getIdentifier(), 0, "Like");
        kbnet.setOutcomeId(video.getIdentifier(), 1, "Dislike");
        kbnet.setNodeBgColor(video.getIdentifier(), Color.BLUE);
        JSONObject node = new JSONObject();
        node.put("name", video.getTitle());
        ids.put(video.getIdentifier(), nodes.size());
        node.put("group", 3);
        nodes.add(node);
    }
    
    public void cloneNetwork(Network net, int u){
        String[] all_nodes = net.getAllNodeIds();
        for(String node: all_nodes){
            String s = node+"_u"+u;
            kbnet.addNode(Network.NodeType.Cpt, s);
            kbnet.setOutcomeId(s, 0, "Like");
            kbnet.setOutcomeId(s, 1, "Dislike");
        }
        for(String node: all_nodes){
            String[] children = net.getChildIds(node);
            for(String child: children){
                kbnet.addArc(node+"_u"+u, child+"_u"+u);
            }
            kbnet.setNodeDefinition(node+"_u"+u, net.getNodeDefinition(node));
        }
    }
    
    public void cloneLayer(Network net, String prefix, int n){
        String[] all_vb_nodes = net.getAllNodeIds();
        for(String vb_node: all_vb_nodes){
            if(vb_node.startsWith("Video")){
                String s = "Group_"+vb_node;
                kbnet.addNode(Network.NodeType.Cpt, s);
                kbnet.setOutcomeId(s, 0, "Like");
                kbnet.setOutcomeId(s, 1, "Dislike");
                double[] probs = new double[n];
                for(int i=1; i<=n; i++){
                    kbnet.addArc(vb_node+"_u"+i, s);
                    probs[i-1] = (0.5+(1.0/n))*(1-relation_uncertainty);
                }
                kbnet.setNodeDefinition(s, getCPT(probs));
            }
        }
    }
    
    /**
     * 
     * @param entity
     * @param parents
     * @throws Exception 
     */
    public void createArc(Entity entity, List<Parent> parents) throws Exception{
        if(parents != null){
            double[] probs = new double[parents.size()];
            int index = 0;
            for(Parent parent: parents){
                String[] echildren = kbnet.getChildIds(entity.getIdentifier());
                if(contains(echildren, parent.getIdentifier()))
                    throw new Exception("ERROR!! Cyclic dependency between "+parent.getIdentifier()+" and "+entity.getIdentifier());
                kbnet.addArc(parent.getIdentifier(), entity.getIdentifier());
                JSONObject link = new JSONObject();
                link.put("source", ids.get(parent.getIdentifier()));
                link.put("target", ids.get(entity.getIdentifier()));
                link.put("value", parent.getProbability());
                links.add(link);
                probs[index++] = parent.getProbability()*(1-relation_uncertainty);
            }
            kbnet.setNodeDefinition(entity.getIdentifier(), getCPT(probs));
        } else {
            kbnet.setNodeDefinition(entity.getIdentifier(), getCPT(new double[0]));
        }
    }
    
    /**
     * 
     * @param video
     * @param parents 
     */
    public void createArc(Video video, List<Parent> parents) {
        if(parents != null){
            double[] probs = new double[parents.size()];
            int index = 0;
            for(Parent parent: parents){
                JSONObject link = new JSONObject();
                link.put("source", ids.get(parent.getIdentifier()));
                link.put("target", ids.get(video.getIdentifier()));
                link.put("value", parent.getProbability());
                links.add(link);
                kbnet.addArc(parent.getIdentifier(), video.getIdentifier());
                probs[index++] = parent.getProbability()*(1-annotation_uncertainty);
            }
            kbnet.setNodeDefinition(video.getIdentifier(), getCPT(probs));
        } else {
            kbnet.setNodeDefinition(video.getIdentifier(), getCPT(new double[0]));
        }
    }
    
    /**
     * Writes the Bayesian Network to a file
     * @param filename 
     * @throws java.io.IOException 
     */
    public void saveKBNetworkAs(String filename) throws IOException {
        System.out.println("Will write the network in "+filename);
        kbnet.writeFile(path+"/"+filename);
        // Write the JSON Object
        net.put("nodes", nodes);
        net.put("links", links);
        FileWriter file = new FileWriter("C:\\Users\\Aya\\Documents\\NetBeansProjects\\Recommender\\public_html\\videobase.json");
        file.write(net.toJSONString());
        file.flush();
    }
}
